﻿using System;
using System.Collections.ObjectModel;
using System.Linq;
using Loki.Engine;

namespace Loki.UI.Bootstrapper
{
    public class ConsoleBootstrapper<TModel, TMainViewModelType> : IBootStrapper<TModel, TMainViewModelType>
        where TMainViewModelType : ViewModel<TModel>
    {

        #region Log
        private static ILog _Log = null;
        /// <summary>
        /// Gets the logger.
        /// </summary>
        /// <value>The logger.</value>
        protected ILog Log
        {
            get
            {
                if (_Log == null)
                    _Log = LokiServices.Logger.GetLogger("Loki.UI.ConsoleBootstrapper");
                return _Log;
            }
        }
        #endregion

        #region Run
        public static void Run(string P_DataDefinitions, string P_ModelDefinitions, string P_ViewModelDefinition, string P_UIDefinitions)
        {
            Run(new WinFormBootStrapper<TModel, TMainViewModelType>(P_DataDefinitions, P_ModelDefinitions, P_ViewModelDefinition, P_UIDefinitions));
        }


        public static void Run(WinFormBootStrapper<TModel, TMainViewModelType> P_Boot)
        {
            Application.SetCompatibleTextRenderingDefault(false);
            // If using EnableVisualStyles, always call DoEvents immediately afterwards.
            // Failing to do so can result in an unexpected SEHException.
            Application.EnableVisualStyles();
            Application.DoEvents();

            P_Boot.Initialize();

            Application.Run(P_Boot);
        }

        public virtual void PreStart()
        {
            LokiCommands.Application.Start.Execute(null);
        }

        public void Start()
        {
            Log.Info("Load main form");
            // Creates main objects
            TMainViewModelType L_MainModel = ContextHelper.GetViewModel<TMainViewModelType>();
            MainView = ContextHelper.GetView<ViewBase>(L_MainModel);

            // activates main form
            MainView.Show();
        }

        public void Exit()
        {
            Log.Info("Application exit");

            Application.Exit();
        }
        #endregion
        private string[] _DataDefinitions;
        private string[] _ModelDefinitions;
        private string[] _VMDefinitions;
        private string[] _UIDefinitions;

       /* protected WinFormBootStrapper(string P_DataDefinitions, string P_ModelDefinitions, string P_ViewModelDefinition, string P_UIDefinitions)
            : this(new string[] { P_DataDefinitions }, new string[] { P_ModelDefinitions }, new string[] { P_ViewModelDefinition }, new string[] { P_UIDefinitions })
        {

        }*/

        protected WinFormBootStrapper(string[] P_DataDefinitions, string[] P_ModelDefinitions, string[] P_ViewModelDefinition, string[] P_UIDefinitions)
        {
            ErrorHandler.RegisterMapper("TUI", new ErrorMessageMapper(Messages.UIErrors.ResourceManager, x => new LokiException(x), (x, y) => new LokiException(x, y)));

            // setting context
            UIContext.Thread = Thread.CurrentThread;
            UIContext.SyncContext = SynchronizationContext.Current;

            _UIDefinitions = P_UIDefinitions;
            _VMDefinitions = P_ViewModelDefinition;
            _ModelDefinitions = P_ModelDefinitions;
            _DataDefinitions = P_DataDefinitions;

            ViewModels = new ObservableCollection<ViewModel<TModel>>();

            Commands = new CommandManager();

            Commands.Handle(LokiCommands.Application.Start, ApplicationStart_Execute);
            Commands.Handle(LokiCommands.Application.Exit, ApplicationExit_Execute);
            Commands.Handle(LokiCommands.Application.Message, ApplicationMessage_Execute);
            Commands.Handle(LokiCommands.Application.Warning, ApplicationWarning_Execute);
            Commands.Handle(LokiCommands.Application.Error, ApplicationError_Execute);

            Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

        }


        void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            LokiCommands.Application.Error.Execute(e.Exception);
            Exit();
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        public virtual void Initialize()
        {
            Log.Info("Initializing application");

            Log.DebugFormat("Loading data definitions from [{0}]", string.Join(", ", _DataDefinitions));
            ContextHelper.LoadData(_DataDefinitions);
            Log.DebugFormat("Loading model definitions from [{0}]", string.Join(", ", _ModelDefinitions));
            ContextHelper.LoadModel(_ModelDefinitions);
            Log.DebugFormat("Loading UI definitions from [{0}]", string.Join(", ", _VMDefinitions));
            ContextHelper.LoadUI(_VMDefinitions);
            Log.DebugFormat("Loading Templates definitions from [{0}]", string.Join(", ", _UIDefinitions));
            ContextHelper.LoadTemplates(_UIDefinitions);

            // start
            Log.Info("Starting application");
             PreStart();
        }

        #region Commands
        private void ApplicationStart_Execute(object sender, CommandEventArgs e)
        {
            Start();
        }

        private void ApplicationExit_Execute(object sender, CommandEventArgs e)
        {
            Exit();
        }

        private void ApplicationMessage_Execute(object sender, CommandEventArgs e)
        {
            Console.WriteLine("Message : {0}",e.Parameter);
        }

        private void ApplicationWarning_Execute(object sender, CommandEventArgs e)
        {
            Console.WriteLine("Warning : {0}",e.Parameter );
        }

        private void ApplicationError_Execute(object sender, CommandEventArgs e)
        {
            Console.WriteLine("Error : {0}",e.Parameter);
            Console.Error.WriteLine("{0}",e.Parameter );
        }

        protected CommandManager Commands { get; private set; }
        #endregion

        #region Model
        public TModel ApplicationModel
        {
            get { return ContextHelper.GetApplicationModel<TModel>(); }
        }
        #endregion

        #region Viewmodels
        /// <summary>
        /// Gets the main view model.
        /// </summary>
        public TMainViewModelType MainViewModel
        {
            get { return MainView.ViewModel as TMainViewModelType; }
        }

        /// <summary>
        /// Gets the view models.
        /// </summary>
        public ObservableCollection<ViewModel<TModel>> ViewModels
        {
            get;
            private set;
        }

        protected TViewModel ActivateOrAdd<TViewModel>(Func<TViewModel, bool> P_Selector = null, Action<TViewModel> P_Initializer = null) where TViewModel : ViewModel<TModel>
        {
            // search
            TViewModel L_Model = null;
            if (P_Selector != null)
                L_Model = ViewModels.Where(x => x is TViewModel).Cast<TViewModel>().FirstOrDefault(x => P_Selector(x));
            else
                L_Model = ViewModels.FirstOrDefault(x => x is TViewModel) as TViewModel;

            // init
            if (L_Model == null)
            {
                L_Model = AddNew<TViewModel>(P_Initializer);
            }
            else
                L_Model.Activate.Execute(null);
            return L_Model;
        }

        protected TViewModel AddNew<TViewModel>(Action<TViewModel> P_Initializer = null) where TViewModel : ViewModel<TModel>
        {
            TViewModel L_NewModel = ContextHelper.GetViewModel<TViewModel>();
            if (P_Initializer != null)
                P_Initializer(L_NewModel);
            AddSubModel(L_NewModel);
            return L_NewModel;
        }

        private void AddSubModel(ViewModel<TModel> P_Model)
        {
            Commands.Handle(P_Model.Close, (s, e) => SubModel_CanClose(P_Model, e), (s, e) => SubModel_Close(P_Model, e));
            ViewModels.Add(P_Model);

            ViewBase L_View = ContextHelper.GetView<ViewBase>(P_Model);
            if (L_View != null)
            {
                L_View.Show();
            }
        }

        private void SubModel_CanClose(ViewModel<TModel> P_Model, CanExecuteCommandEventArgs e)
        {
            e.CanExecute = true;
        }

        private void SubModel_Close(ViewModel<TModel> P_Model, CommandEventArgs P_Args)
        {
            Commands.UnHandle(P_Model.Close);
            ViewModels.Remove(P_Model);
        }

        #endregion
    }
}
